/*
 * Isomorphic SmartClient
 * Version 6.5 (2008-04-30)
 * Copyright(c) 1998-2007 Isomorphic Software, Inc. All rights reserved.
 * "SmartClient" is a trademark of Isomorphic Software, Inc.
 *
 * licensing@smartclient.com
 *
 * http://smartclient.com/license
 */

/*
 *
 * Portions of this file are Copyright(c) 2008 Erich Regen.
 * 
 * Portions of this file are from http://www.JSON.org/json2.js which is in the
 * public domain. It was written by Douglas Crockford. Please see below for
 * details. 
 * 
 * Portions of this file are Copyright (c) 2001-2007, TIBCO Software Inc.      
 *  
*/
 
// Visit http://www.smartclient.com for more information on Isomorphic SmartClient 

//> @class CustJsonRPCDataSource
// The CustJsonRPCDataSource implements the 4 core DataSource operations using the JSON-RPC
// protocol.
// <P>
// The currently supported protocol version is 2.0 Draft only. Its specification can be
// found under:<br>
// +externalLink{http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal, JSON-RPC 2.0 Draft}
// and +externalLink{http://groups.google.com/group/json-rpc/web/json-rpc-over-http, JSON-RPC over http}.
// <P>
// Please make sure you understand how 
// +externalLink{http://getahead.org/blog/joe/2007/01/01/csrf_attacks_or_how_to_avoid_exposing_your_gmail_contacts.html, CSRF attacks} and
// +externalLink{http://www.fortify.com/servlet/downloads/public/JavaScript_Hijacking.pdf, JavaScript Hijacking}
// works before you use JSON-RPC. These type of attacks can concern you even if your
// client and server are in a secure lan.
// <P>
// A CustJsonRPCDataSource is used just like a normal DataSource.  CustJsonRPCDataSources are configured,
// using the general-purpose databinding facilities of DataSources, to expect a particular
// format for responses and to send requests in a specific format.
// <P>

//TODO: document! The rest are old comments from RestDataSource!


// CustJsonRPCDataSource is typically used with PHP, Ruby, Python, Perl or custom server technologies,
// and represents an alternative to installing the SmartClient Server in a Java technology
// stack, or using +link{group:wsdlBinding,WSDL-based binding} with .NET or other WSDL-capable
// technologies.
// <P>
// <span style="font-weight:bold;font-size:16px;">Examples</span>
// <p>
// <b>XML formatted responses:</b>
// <P>
// CustJsonRPCDataSource expects a response to like the following in response to a "fetch" request:
// <pre>
// &lt;response&gt;
//    &lt;status&gt;0&lt;/status&gt;
//    &lt;data&gt;
//      &lt;record&gt;
//          &lt;field1&gt;value&lt;/field1&gt;
//          &lt;field2&gt;value&lt;/field2&gt;
//      &lt;/record&gt;
//      &lt;record&gt;
//          &lt;field1&gt;value&lt;/field1&gt;
//          &lt;field2&gt;value&lt;/field2&gt;
//      &lt;/record&gt;
//    &lt;/data&gt;
// &lt;/response&gt;
// </pre>
// The status element indicates whether the fetch operation was successful 
// (see +link{group:statusCodes}).
// <P>
// The data element contains a list of record nodes, each of which represents a record returned
// by the server.  Additional elements for totalRows returned, startRow and endRow may
// also be included.
// <P>
// Note: for a more compact format, simple field values may be specified on record 
// nodes directly as attributes - in this case a record element might be structured like this:
// <pre>
//     &lt;record field1="value" field2="value" /&gt;
// </pre>
// <p>
// Successful "add" or "update" request responses are similar in format - in this case the data
// element would be expected to contain a single record object containing the details of the
// record, as saved on the server.
// <P>
// The response from a "remove" operation would again include status and data elements, but in
// this case, only the primary key field value(s) of the removed record would be expected to be 
// present under the data element.
// <p>
// If a validation failure occurred on the server, the response would
// have status set to +link{RPCResponse.STATUS_VALIDATION_ERROR} [<code>-4</code> by default],
// and any validation errors could be included as per-field sub-elements of an "errors"
// element.
// <P>
// Note that in this case the response would not be expected to contain any data element.  
// A response showing a validation error might look like this:
// <pre>
// &lt;response&gt;
//    &lt;status&gt;-4&lt;/status&gt;
//    &lt;errors&gt;
//      &lt;field1&gt;
//          &lt;errorMessage&gt;A validation error occurred for this field&lt;/errorMessage&gt;
//      &lt;/field1&gt;
//    &lt;/errors&gt;
// &lt;/response&gt;
// </pre>
// <p>
// <b>JSON formatted responses:</b>
// <P>
// JSON format responses are expected to contain the same data / meta-data as XMLresponses,
// encapsulated a simple object with a <code>"response"</code> attribute.<br>
// The response to a "fetch" request would therefore have this format:<br>
// <pre>
// {    response:
//      {   status:0,
//          data:[
//              {field1:"value", field2:"value"},
//              {field1:"value", field2:"value"}
//          ]
//      }
// }
// </pre>
// The structure successful for "add", "update" and "remove" responses would be similar, though
// the data array would be expected to contain the single edited record only.<br>
// For a remove, only the value for the primaryKey field[s] would be required.
// <p>
// If an error occurred on the server, this would be indicated in the <code>status</code> 
// attribute.  For a validation error, the status would be set to 
// +link{RPCResponse.STATUS_VALIDATION_ERROR} [<code>-4</code> by default], and errors would
// be specified in the <code>errors</code> attribute of the response. For example:
// <pre>
// {    response:
//      {   status:-4,
//          errors: 
//              {   field1:{errorMessage:"A validation error on field1"},
//                  field2:{errorMessage:"A validation error on field2"}
//              }
//      }
// }
// </pre>
// An array of errors may also be returned for a single field, like this:
// <pre>
// {    response:
//      {   status:-4,
//          errors: 
//              {   field1:[
//                      {errorMessage:"First error on field1"},
//                      {errorMessage:"Second error on field1"}
//                  ]
//              }
//      }
// }
// </pre>
// <b>Server inbound data formats</b>
// <P>
// The format of data sent to the server is determined by the +link{OperationBinding.dataProtocol}
// specified for the operation. Request data is sent as parameters if the format is 
// specified as <code>"getParams"</code> or <code>"postParams"</code>.
// <P>
// In this case, the parameters sent to the server will consist of the DSRequest's data, and any
// parameters explicitly specified on the DSRequest object (as +link{RPCRequest.params}.<br> 
// If +link{CustJsonRPCDataSource.sendMetaData} is true, the DSRequest meta 
// data properties will also be present as parameters, prefixed with 
// +link{CustJsonRPCDataSource.metaDataPrefix}.
// <P>
// Example URL constructed with the metaDataPrefix set to <code>"_"</code> (the default):
// <p>
// <code>
// &nbsp;&nbsp;&nbsp;<i>[dataURL]</i>?field1=value1&_operationType=fetch&_startRow=0&_endRow=50&_sortBy=-field2&_dataSource=dsName
// </code>
// <p>
// In this case the server would be able to separate the request's data from the meta data 
// via the <code>"_"</code> prefix.
// <P>
// If data is sent to the server via the <code>"postMessage"</code> dataProtocol, the data will
// be sent as a serialized XML message.  In this case the created XML document will contain 
// nodes for each relevant property of the request (including a <code>"data"</code> node 
// containing the request's data.
// <P>
// An example of an XML message might look like this:
// <pre>
//  &lt;data&gt;
//        &lt;countryDS&gt;
//            &lt;countryCode&gt;US&lt;/countryCode&gt;
//            &lt;countryName&gt;Edited Value&lt;/countryName&gt;
//            &lt;capital&gt;Edited Value&lt;/capital&gt;
//            &lt;continent&gt;Edited Value&lt;/continent&gt;
//        &lt;/countryDS&gt;
//    &lt;/data&gt;
//    &lt;dataSource&gt;countryDS&lt;/dataSource&gt;
//    &lt;operationType&gt;update&lt;/operationType&gt;
// </pre>
// The +link{CustJsonRPCDataSource.operationBindings,default OperationBindings} for a CustJsonRPCDataSource
// specify dataProtocol as "getParams" for the fetch operation, and "postParams" for update,
// add and remove operations.
// <P>
// <b>Hierachical (Tree) data:</b>
// <P>
// To create a hierachical DataSource, in the DataSource's <code>fields</code> array, a field 
// must be specified as the parent id field - the field which will contain a pointer to the
// id of each node's parent. 
// This can be achieved by setting the +link{DataSourceField.foreignKey} and the 
// +link{DataSourceField.rootValue} attributes on the field definition. For example:
// <pre>
// CustJsonRPCDataSource.create({
//  ID:"supplyItem",
//  fields : [
//      {name:"itemId", type:"sequence", primaryKey:true},
//      {name:"parentId", type:"integer", foreignKey:"supplyItem.itemId", rootValue:0},
//      ...
//  ]
// });
// </pre>
// Tree Data is then treated on the server as a flat list of records linked by parent id.<br>
// Tree data is typically displayed using a dataBound +link{class:TreeGrid} component. TreeGrids
// automatically create a +link{class:ResultTree} data object, which requests data directly
// from the DataSource.  ResultTrees load data on demand, only requesting currently visible 
// (open) nodes from the server. This is handled by including a specified value for the parent 
// id field in the request criteria.<br>
// To implement a standard load-on-demand tree CustJsonRPCDataSource back end, you should therefore 
// simply return the set of nodes that match the criteria passed in. 
// For example, if your DataSource was defined as the "supplyItem" code snippet above, 
// a fetch request for all children of a node with <code>itemId</code> set to <code>12</code> 
// would have <code>"parentId"</code> set to <code>12</code> in the request criteria.
// A valid response would then contain all the records that matched this criteria. For example:
// <pre>
// &lt;response&gt;
//    &lt;status&gt;0&lt;/status&gt;
//    &lt;data&gt;
//      &lt;record&gt;
//          &lt;itemId&gt;15&lt;/itemId&gt;
//          &lt;parentId&gt;12&lt;/parentId&gt;
//      &lt;/record&gt;
//      &lt;record&gt;
//          &lt;itemId&gt;16&lt;/itemId&gt;
//          &lt;parentId&gt;12&lt;/parentId&gt;
//      &lt;/record&gt;
//    &lt;/data&gt;
// &lt;/response&gt;
// </pre>
// The structure of responses for Add, Update and Delete type requests will be the 
// same regardless of whether the data is hierachical. However you should be aware that 
// the underlying data storage may need to be managed slightly differently in some cases.<br>
// Specifically, Add and Update operations may change the structure of the tree by returning a 
// new parent id field value for the modified node. Depending on how your data is stored you 
// may need to include special back-end logic to handle this.<br>
// Also, if a user deletes a folder within a databound tree, any children of that folder will 
// also be dropped from the tree, and can be removed from the back-end data storage.<br>
// Note: For a general overview of binding components to Tree structured data, see 
// +link{group:treeDataBinding, Tree Databinding}.
// 
// @treeLocation Client Reference/Data Binding
// @visibility external
// @example restEditSave
//<
isc.defineClass("CustJsonRPCDataSource", "DataSource");


isc.CustJsonRPCDataSource.addProperties({
    serverType:"generic",

    //> @attr    CustJsonRPCDataSource.dataFormat   (string : "json" : IR)
    // Expected format for server responses. CustJsonRPCDataSources handle <code>"json"</code>
    // format responses only. See class overview documentation for 
    // examples of responses.
    // @visibility external
    //<
    dataFormat:"json",
    

    //> @attr    CustJsonRPCDataSource.jsonRPCVersion   (string : "2.0" : IR)
    // Version of the JSON-RPC protocol to use when encoding and decoding
    // messages and contacting the server.<br>
    // Currently the following values are supported:<br>
    // <code>"2.0"</code><br>
    // Version 2.0 is currently a draft. You can find its specification under
    // +externalLink{http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal, JSON-RPC 2.0 Draft}
    // and +externalLink{http://groups.google.com/group/json-rpc/web/json-rpc-over-http, JSON-RPC over http}.
    // @visibility external
    //<
    jsonRPCVersion:"2.0",


    //> @attr CustJsonRPCDataSource.jsonRecordXPath    (string : "/result/data" : IR)
    // <code>recordXPath</code> mapping to the data node of json returned by the server.
    // Applies if this.dataFormat is set to <code>"json"</code><br>
    // The default value will pick up data from a response structured as follows:<br>
    // <pre>
    // {response:
    //  {status:0,
    //   data:[
    //      {field1:"value", field2:"value"},
    //      {field1:"value", field2:"value"}
    //   ]
    // }
    // </pre>
    // @visibility external
    //<
    jsonRecordXPath:"/result/data",
    
    // Overrid init to pick up these paths
    init : function () {
        this.recordXPath = this.jsonRecordXPath;
        return this.Super("init", arguments);
    },
    
    //> @attr CustJsonRPCDataSource.operationBindings (Array of OperationBinding : [...] : IR)
    // CustJsonRPCDataSource OperationBindings set to specify default dataProtocol per operationType.
    // Default databindings are:
    // <pre>
    // [ {operationType:"fetch", dataProtocol:"getParams"},
    //   {operationType:"add", dataProtocol:"postParams"},
    //   {operationType:"remove", dataProtocol:"postParams"},
    //   {operationType:"update", dataProtocol:"postParams"} ];
    // </pre>
    // @visibility external
    //<
    // TODO: Specifying a default here does not make much sense. If not set
    //       before we would miss serverMethod (important) in any case and
    //       cannot set it here since we do not know it.
    //       But this also means that dataProtocol defaults to not very
    //       useful values.
    //       Is there a better way?
    operationBindings:[
       {operationType:"fetch", dataProtocol:"postMessage"},
       {operationType:"add", dataProtocol:"postMessage"},
       {operationType:"remove", dataProtocol:"postMessage"},
       {operationType:"update", dataProtocol:"postMessage"}
    ],
    
    //> @attr CustJsonRPCDataSource.dataURL  (string : null : IR)
    // Default URL to contact to fulfill all DSRequests.  
    // CustJsonRPCDataSources also allow per-operationType dataURLs to be set via
    // <ul>
    // <li>+link{CustJsonRPCDataSource.fetchDataURL}</li>
    // <li>+link{CustJsonRPCDataSource.addDataURL}</li> 
    // <li>+link{CustJsonRPCDataSource.updateDataURL}</li>
    // <li>+link{CustJsonRPCDataSource.removeDataURL}</li>
    // </ul>
    // @visibility external
    //<
    
    //> @attr CustJsonRPCDataSource.fetchDataURL    (string : null : IR)
    // Custom dataURL for fetch type operations
    // @visibility external
    //<

    //> @attr CustJsonRPCDataSource.updateDataURL    (string : null : IR)
    // Custom dataURL for update type operations
    // @visibility external
    //<
    
    //> @attr CustJsonRPCDataSource.addDataURL    (string : null : IR)
    // Custom dataURL for add type operations
    // @visibility external
    //<
    
    //> @attr CustJsonRPCDataSource.removeDataURL    (string : null : IR)
    // dataURL for fetch type operations
    // @visibility external
    //<
    
    //> @attr CustJsonRPCDataSource.sendMetaData (boolean : true : IR)
    // Should  operation meta data be included when assmebling parameters to send 
    // to the server? If true, meta data parameters will be prefixed with the 
    // +link{CustJsonRPCDataSource.metaDataPrefix}.<br>
    // Applies to operations where OperationBinding.dataProtocol is set to 
    // <code>"getParams"</code> or <code>"postParams"</code> only.
    // @visibility external
    //<
    sendMetaData:true,

    //> @attr CustJsonRPCDataSource.metaDataPrefix   (string : "_" :IR)
    // I +link{CustJsonRPCDataSource.sendMetaData} is true, this attribute is used to specify
    // the prefix to apply to 'meta data' properties when assembling parameters to send to the 
    // server.  Applies to operations where OperationBinding.dataProtocol is set to 
    // <code>"getParams"</code> or <code>"postParams"</code> only.
    // @visibility external
    //<
    metaDataPrefix:"_",
    
    // getDataURL() 
    // overridden to respect fetchDataURL et al.
    getDataURL : function (dsRequest) { 
        var type = dsRequest.operationType;

        if (type == "fetch" && this.fetchDataURL != null) 
            return this.fetchDataURL;
        if (type == "update" && this.updateDataURL != null)
            return this.updateDataURL;
        if (type == "add" && this.addDataURL != null) 
            return this.addDataURL;
        if (type == "remove" && this.removeDataURL != null)
            return this.removeDataURL;
        return this.Super("getDataURL", arguments);
   },

    //> @method CustJsonRPCDataSource.transformRequest()
    // CustJsonRPCDataSource.transformRequest() implemented to format request meta-data properties 
    // for transmitting to the server.<br>
    // See +link{class:CustJsonRPCDataSource, CustJsonRPCDataSource overview} for a description of the 
    // standard formatting applied to requests.
    // @visibility external
    //<
    transformRequest : function (dsRequest) {
        var protocol = this.getDataProtocol(dsRequest);
        if (protocol == "postMessage" || protocol == "getParams") {
            // All fields passed in as 'data' will be available directly as parameters
            // Also include any explicit parameters present on the dsRequest
            var inputParams = isc.addProperties({}, dsRequest.data, dsRequest.params);
            
            var result = {
                // ATTENTION: For http GET this is an assumption that json-rpc
                //            over http also should include the jsonrpc
                //            parameter! The specs are written for obsolete 1.2
                //            json-rpc where it made sense. Applied on the 2.0
                //            specs it is unclear from my point of view whether
                //            this parameter should be included.
                jsonrpc: "2.0",
                method: this.getOperationBinding(dsRequest).serverMethod,
                // FIXME: which id to choose???
                // RPCResponse.transactionNum seems to be what we need but is
                // not available here.
                // On the other side if RPCManager handles request-response
                // correlation(?) we need id just to satisfy JSON-RPC specs.
                id: 1,
                params: inputParams
            };
            // Attach meta data parameters to the transaction
            if (this.sendMetaData) {
                if (!this.parameterNameMap) {
                    var map = {};
                    
                    map[this.metaDataPrefix  + "operationType"] = "operationType";
                    map[this.metaDataPrefix + "operationID"] = "operationID";
                    map[this.metaDataPrefix + "startRow"] = "startRow";
                    map[this.metaDataPrefix + "endRow"] = "endRow";
                    map[this.metaDataPrefix + "sortBy"] = "sortBy";
                    map[this.metaDataPrefix + "textMatchStyle"] = "textMatchStyle";
                    map[this.metaDataPrefix + "clientContext"] = "clientContext";
                    map[this.metaDataPrefix + "oldValues"] = "oldValues";
                    this.parameterNameMap = map;
                }
                
                // Meta data will be available as parameters with the metaDataPrefix applied
                for (var parameterName in this.parameterNameMap) {
                    var value = dsRequest[this.parameterNameMap[parameterName]];
                    if (value != null) result.params[parameterName] = value;
                }
                result.params[this.metaDataPrefix + "dataSource"] = this.getID();
            };
            
            dsRequest.contentType = "application/json-rpc";
            dsRequest.httpHeaders = { "Accept" : "application/json-rpc" };

            
            // TODO: is date encoding correct? NOT TESTED in any way!
            if (protocol == "getParams") {
            // JSON-RPC over http for GET demands encoding of param value this way:
            //   1. Base 64 param value only
            //   2. URL Encoded Base 64 param value only
            // url encoding is not necessary in our case since RPCManager(??)
            // does this for us.
              result.params = Base64.strEncode(JSON.stringify(result.params));
              encodedResult = result;
            } else {
              encodedResult = JSON.stringify(result);
            }
            //this.logWarn("Params to send: " + encodedResult);

            return encodedResult;
            
        } else {
            this.logWarn("CustJsonRPCDataSource operation:"+ dsRequest.operationID + ", of type " 
                         + dsRequest.operationType + " has dataProtocol specified as '" 
                         + protocol + "'. Supported protocols are 'postMessage' and 'getParams' "
                         + "only. Defaulting to 'postMessage'.");
        }
        
        
    },



    // Helper method to verify the status returned by the server is valid
/*    getValidStatus : function (status) {
        if (isc.isA.String(status)) {
            if (parseInt(status) == status) status = parseInt(status);
            else {
               status = isc.DSResponse[status];
                if (status == null) {
                    this.logWarn("Unable to map response code: " + status
                                  + " to a DSResponse code, setting status to DSResponse.STATUS_FAILURE.");
                    status = isc.DSResponse.STATUS_FAILURE;
                }
            }
        }
        if (status == null) status = isc.DSResponse.STATUS_SUCCESS;
        return status;
    },
*/


    // smartclient status codes
    //
    // STATUS_FAILURE                       = generic failure on the server
    // STATUS_LOGIN_INCORRECT
    // STATUS_LOGIN_REQUIRED
    // STATUS_MAX_LOGIN_ATTEMPTS_EXCEEDED
    // STATUS_SERVER_TIMEOUT
    // STATUS_SUCCESS
    // STATUS_TRANSPORT_ERROR               = HTTP response other than 200. =>
    //                                        rpcResponse.httpResponseCode 
    // STATUS_VALIDATION_ERROR

    // json-rpc status codes
    //
    // 200  -        ok
    // 204  -        ok for notification
    // 500  -32700   Parse error.       Invalid JSON. An error occurred on the server while parsing the JSON text.
    // 400  -32600   Invalid Request.   The received JSON not a valid JSON-RPC Request.
    // 404  -32601   Method not found.  The requested remote-procedure does not exist / is not available.
    // 500  -32602   Invalid params.    Invalid method parameters.
    // 500  -32603   Internal error.    Internal JSON-RPC error.
    // 500  -32099..-32000 Server error.    Reserved for implementation-defined server-errors.
    //
    //
    //  in our case:
    //    server specific json-rpc errors should be in the error object as
    //      specified in the json-rpc specs.
    //    application specific error codes/messages should be encoded in
    //      the result object/array and should be encoded as smartclient
    //      normally does under /response/errors/*

    _httpResponseCode2msg : function (code) {
        switch (code) {
          case 200:
            result = "ok";
            break;
          case 204:                       // response to a notification
            result = "ok";
            break;
          case 500:
            result = "-32700, parse error.";
            break;
          case 400:
            result = "-32600, invalid Request.";
            break;
          case 404:
            result = "-32601, method not found.";
            break;
            // Since we consult this function only in case of missing result
            // _and_ error parameter it does not make sense to check for the
            // other error codes here. They would be inconclusive. E.g. http
            // error code 500 (information we get) for json-rpc error code
            // -32602 (which we do not know here) looks like 500/-32700
            // above.
          default:
            result = "-99999, unknown http error.";
            break;
        }
    },

    // Helper method to verify the status returned by the server is valid
    _getValidStatus : function (status) {
        if (isc.isA.String(status)) {
            if (parseInt(status) == status) status = parseInt(status);
            else {
                matchedStatus = isc.DSResponse[status];
                if (status == null) {
                    this.logWarn("Unable to map response code: " + status
                                  + " to a DSResponse code, setting status to DSResponse.STATUS_FAILURE.");
                    status = isc.DSResponse.STATUS_FAILURE;
                } else {
                  status = matchedStatus;
                }
            }
        }
        if (status == null) status = isc.DSResponse.STATUS_SUCCESS;
        return status;
    },

    _getValidResponse : function (result, error) {
      if (result != undefined) {
          if (isc.isA.Object(result)) {
              if (result.status != undefined && result.data != undefined) {
                  result = {status: this._getValidStatus(result.status),
                            error: result.data,
                            data: result.data};
              } else {
                  if (result.status != undefined && result.errors != undefined){
                      //this.logWarn("result: " + result + ", result.status " + result.status);
                      result = {status: this._getValidStatus(result.status),
                                error: result.errors
                                //data: result.data
                                };
                  }
                   else {
                      result = {status: isc.DSResponse.STATUS_FAILURE,
                                error: "Invalid JSON received.",
                                data: "Invalid JSON received."};
                  }
              }
              
          } else {
              result = {status: isc.DSResponse.STATUS_FAILURE,
                        error: "Invalid JSON received.",
                        data: "Invalid JSON received."};
          }
      }
      else {
          if (error != undefined) {
              result = {status: isc.DSResponse.STATUS_FAILURE,
                        error: "Error: " + error.code + ", " + error.message +
                             " - " + error.data,
                        data: "Error: " + error.code + ", " + error.message +
                             " - " + error.data};
          }
          else {
              httpStatus = this.httpResponseCode;
              result = {status: isc.DSResponse.STATUS_TRANSPORT_ERROR,
                        error: this._httpResponseCode2msg(httpStatus),
                        data: this._httpResponseCode2msg(httpStatus)};
          }
      }
      
      return result;
    },



    // From DataSource.js. Modified to work here in case of a http 404 or
    // similar. Only to be used if dataFormat = "custom".
    // Furthermore "custom" should only be used as long as there is a bug
    // in smartclient regarding handling of http status != 200 for
    // dataFormat = "json". After this bug is fixed "custom" shoud not be used
    // and this function is not needed anymore.
    _handleJSONReply : function (rpcResponse, data, rpcRequest) {
        var dsRequest = rpcRequest,
            recordXPath = this.getOperationBinding(dsRequest).recordXPath;
        var dsResponse = rpcResponse;

        // log the raw object reply if we didn't just eval it from text (if we eval it from
        // text we've already logged the raw response)
        if (rpcResponse._wasJSONTextReply && this.logIsDebugEnabled("xmlBinding")) {
            this.logDebug("Raw response data: " + 
                           isc.Comm.serialize(data, true), "xmlBinding");
        }

        // Hang onto the raw JSON object returned by the server. This allows transformResponse
        // to actually do something useful with the data returned (EG, pick up meta-data)
        var rawData = data;
        if (this.recordXPath) {
            // extract relevant records via XPath
            data = isc.xml.selectObjects(data, this.recordXPath);
            this.logInfo("JSON recordXPath: '" + this.recordXPath + 
                         "', selected: " + this.echoLeaf(data), "xmlBinding");
        }

        data = this.recordsFromObjects(data);

        if (this.logIsDebugEnabled("xmlBinding")) {
            this.logDebug("Validated dsResponse.data: " + 
                           isc.Comm.serialize(data, true), "xmlBinding");
        }

        // derive the default response

        dsResponse.data = data;
        dsResponse.startRow = dsRequest.startRow || 0; 
        dsResponse.status = 0; // assume success
   

        dsResponse.endRow = dsResponse.startRow + Math.max(0, data.length);
        dsResponse.totalRows = Math.max(dsResponse.endRow, data.length);

        var result = {"dsResponse": dsResponse, "dsRequest": dsRequest, "rawData": rawData};
        return result;
    },


    // From DataSource.js. Modified to handle array in case of a http 404 or
    // similar. Only to be used if dataFormat = "custom".
    // Furthermore "custom" should only be used as long as there is a bug
    // in smartclient regarding handling of http status != 200 for
    // dataFormat = "json". After this bug is fixed "custom" shoud not be used
    // and this function is not needed anymore.
    _handleJSONTextReply : function (rpcResponse, jsonText, rpcRequest) {
        //!DONTOBFUSCATE
        if (isc.isA.String(jsonText)) {
            var evalText = jsonText;
            // Strip the json prefix / suffix if supplied, and transport was not scriptInclude
            if (rpcRequest.transport != "scriptInclude") {
                // Strip whitespace before checking for specified prefix / suffix
                var re;            
                if (this.jsonPrefix) {
                    re = new RegExp(/^\s*/);
                    evalText = evalText.replace(re, "");
                    if (evalText.startsWith(this.jsonPrefix)) {
                        evalText = evalText.substring(this.jsonPrefix.length);
                    } else {
                        this.logInfo("DataSource specifies jsonPrefix, but not present in " + 
                                     "response returned from server. Processing response anyway.");
                    }
                } 
                if (this.jsonSuffix) {
                    re = new RegExp(/\s*$/)                
                    evalText = evalText.replace(re, "");            
                    if (evalText.endsWith(this.jsonSuffix)) {
                        evalText = evalText.substring(0, (evalText.length - this.jsonSuffix.length));
                    } else {
                        this.logInfo("DataSource specifies jsonSuffix, but not present in " + 
                                     "response returned from server. Processing response anyway.");
                    }
                }
            }
        
            // Note: FF1.0.7 bug: curly must be escaped in the following regex.  Not an issue in IE
            // or FF1.5
            if (evalText.match(/^\s*\{/)) {
                // eval barfs on "{ ...  }", thinking it's a closure
                evalText = "var evalText = " + evalText + ";evalText;"
            } 
            try {
                var jsonObjects = eval(evalText);
            } catch (e) {
                this.logWarn("Error evaluating JSON: " + e.toString() + ", JSON text:\r" + jsonText);
                return;
            }
            
            // warn and bail if server response object evals to null.
            // This can happen if the response is "", or if it's something like "var foo = {};"
            // where the result of the eval is undef.
            // Avoids a JS error in handleJSONReply
            if (jsonObjects == null) {
                this.logWarn("Evaluating JSON reply resulted in empty value. JSON text:\r" 
                             + this.echo(jsonText));
                return;
            }
        // we probably got an error array from RPCManager (?)
        } else if (isc.isA.Array(jsonText)) {
            if (isc.isA.String(jsonText[0].data) &&
                isc.isA.Number(jsonText[0].status)) {
              // try to put that information into the json-rpc error object
              // assuming that the error array we got is structured the expected
              // way. TODO: maybe some more tests.
              var jsonObjects = {"jsonrpc": "2.0", "error":
                                    {"code": jsonText[0].status,
                                     "message": jsonText[0].data}, "id": null};
            } else {
              var jsonObjects = {"jsonrpc": "2.0", "error":
                                      {"code": -32099,
                                      "message": "Could not parse answer from server."},
                                 "id": null};
              this.logWarn("Evaluating JSON reply resulted in unidentified data. JSON text:\r" 
                             + this.echo(jsonText));
            }
        } else {
            var jsonObjects = {"jsonrpc": "2.0", "error":
                                    {"code": -32099,
                                    "message": "Could not parse answer from server."},
                               "id": null};
            this.logWarn("Evaluating JSON reply resulted in unidentified data. JSON text:\r" 
                           + this.echo(jsonText));
        }
        // Hack to let _handleJSONReply know this JSON response came from text, for logging
        // purposes
        rpcResponse._wasJSONTextReply = true; 

        return this._handleJSONReply(rpcResponse, jsonObjects, rpcRequest);

    },




    _transformResponse : function (dsResponse, dsRequest, data) {
        if (this.dataFormat == "json" || this.dataFormat == "custom") {
            var rawResponse = data || {};
            var validResponse = this._getValidResponse(rawResponse.result, rawResponse.error);
            dsResponse.status = validResponse.status;
            
            // if the status is a validation error, convert the errors from json
            if (dsResponse.status == isc.DSResponse.STATUS_VALIDATION_ERROR) {
                var errors = validResponse.error;
                // Handle being returned an array of errors (per row) or a single error object
                // for the modified row.
                if (isc.isAn.Array(errors)) {
                    if (errors.length > 1) {
                        this.logWarn("server returned an array of errors - ignoring all but the first one");
                    }
                    errors = errors[0];   
                }
                dsResponse.errors = errors;
                // TODO: is it correct here to set totalRows and endRow to 0?
                dsResponse.totalRows = dsResponse.endRow = 0;
            // handle other error status (status < 0)
            } else {
                if (dsResponse.status < 0) {
                    dsResponse.data = validResponse.error;
                    // TODO: is it correct here to set totalRows and endRow to 0?
                    dsResponse.totalRows = dsResponse.endRow = 0;
                } else {
                    dsResponse.data = validResponse.data;
                }
            }
              
            if (rawResponse.totalRows != null) dsResponse.totalRows = rawResponse.totalRows;
            if (rawResponse.startRow != null) dsResponse.startRow = rawResponse.startRow;
            if (rawResponse.endRow != null) dsResponse.endRow = rawResponse.endRow;
            
        }
        //this.logWarn("dsResponse.status: " + dsResponse.status + ", dsResponse.data: " + dsResponse.data + ", dsResponse.error: " + dsResponse.error);
        //this.logWarn("dsResponse.totalRows: " + dsResponse.totalRows + ", dsResponse.startRow: " + dsResponse.startRow + ", dsResponse.endRow: " + dsResponse.endRow);
        return dsResponse;
    },


    //> @method CustJsonRPCDataSource.transformResponse()
    // CustJsonRPCDataSource.transformResponse() implemented to extract data and meta-data properties
    // from the XML Response provided by the server.<br>
    // See +link{class:CustJsonRPCDataSource, CustJsonRPCDataSource overview} for a description of the 
    // standard data format expected to returned from the server.
    // @visibility external
    //<

    transformResponse : function (dsResponse, dsRequest, data) {
        // ATTENTION: We assume here that server errors will be included in the
        //            http body. json-rpc over http specs currently do not
        //            say anything about that.
        //            We also assume that application level errors will be
        //            sent as a successful rpc call (result instead of error
        //            object) and contain further error code and error message
        //            in the result parameter. 
        if (this.dataFormat == "custom") {
            dsRequest.recordXPath = this.recordXPath
            var handleResult = this._handleJSONTextReply(dsResponse, data, dsRequest);
            return (this._transformResponse(handleResult.dsResponse, handleResult.dsRequest, handleResult.rawData));
            //this.logWarn("data: " + data);
        } else {
            return (this._transformResponse(dsResponse, dsRequest, data));
        }
    }

});


////////////////////////////////////////////////////////////////////////////////
//
// The following code is the original json2.js from:
//    http://www.JSON.org/json2.js
// just copied into this file.
// License: Public Domain. Author: Douglas Crockford
////////////////////////////////////////////////////////////////////////////////

/*
    http://www.JSON.org/json2.js
    2008-05-25

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects without a toJSON
                        method. It can be a function or an array.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array, then it will be used to
            select the members to be serialized. It filters the results such
            that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
    charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
    getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
    parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
    test, toJSON, toString
*/

if (!this.JSON) {

// Create a JSON object only if one does not already exist. We create the
// object in a closure to avoid creating global variables.

    JSON = function () {

        function f(n) {
            // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            gap,
            indent,
            meta = {    // table of character substitutions
                '\b': '\\b',
                '\t': '\\t',
                '\n': '\\n',
                '\f': '\\f',
                '\r': '\\r',
                '"' : '\\"',
                '\\': '\\\\'
            },
            rep;


        function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

            escapeable.lastIndex = 0;
            return escapeable.test(string) ?
                '"' + string.replace(escapeable, function (a) {
                    var c = meta[a];
                    if (typeof c === 'string') {
                        return c;
                    }
                    return '\\u' + ('0000' +
                            (+(a.charCodeAt(0))).toString(16)).slice(-4);
                }) + '"' :
                '"' + string + '"';
        }


        function str(key, holder) {

// Produce a string from holder[key].

            var i,          // The loop counter.
                k,          // The member key.
                v,          // The member value.
                length,
                mind = gap,
                partial,
                value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

            if (value && typeof value === 'object' &&
                    typeof value.toJSON === 'function') {
                value = value.toJSON(key);
            }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

            if (typeof rep === 'function') {
                value = rep.call(holder, key, value);
            }

// What happens next depends on the value's type.

            switch (typeof value) {
            case 'string':
                return quote(value);

            case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
            case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

                return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

            case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

                if (!value) {
                    return 'null';
                }

// Make an array to hold the partial results of stringifying this object value.

                gap += indent;
                partial = [];

// If the object has a dontEnum length property, we'll treat it as an array.

                if (typeof value.length === 'number' &&
                        !(value.propertyIsEnumerable('length'))) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                    v = partial.length === 0 ? '[]' :
                        gap ? '[\n' + gap +
                                partial.join(',\n' + gap) + '\n' +
                                    mind + ']' :
                              '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }

// If the replacer is an array, use it to select the members to be stringified.

                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value, rep);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {

// Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value, rep);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

                v = partial.length === 0 ? '{}' :
                    gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                            mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
            }
        }

// Return the JSON object containing the stringify and parse methods.

        return {
            stringify: function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

                var i;
                gap = '';
                indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

                if (typeof space === 'number') {
                    for (i = 0; i < space; i += 1) {
                        indent += ' ';
                    }

// If the space parameter is a string, it will be used as the indent string.

                } else if (typeof space === 'string') {
                    indent = space;
                }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

                rep = replacer;
                if (replacer && typeof replacer !== 'function' &&
                        (typeof replacer !== 'object' ||
                         typeof replacer.length !== 'number')) {
                    throw new Error('JSON.stringify');
                }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

                return str('', {'': value});
            },


            parse: function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

                var j;

                function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                    var k, v, value = holder[key];
                    if (value && typeof value === 'object') {
                        for (k in value) {
                            if (Object.hasOwnProperty.call(value, k)) {
                                v = walk(value, k);
                                if (v !== undefined) {
                                    value[k] = v;
                                } else {
                                    delete value[k];
                                }
                            }
                        }
                    }
                    return reviver.call(holder, key, value);
                }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

                cx.lastIndex = 0;
                if (cx.test(text)) {
                    text = text.replace(cx, function (a) {
                        return '\\u' + ('0000' +
                                (+(a.charCodeAt(0))).toString(16)).slice(-4);
                    });
                }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                    j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                    return typeof reviver === 'function' ?
                        walk({'': j}, '') : j;
                }

// If the text is not JSON parseable, then a SyntaxError is thrown.

                throw new SyntaxError('JSON.parse');
            }
        };
    }();
};


////////////////////////////////////////////////////////////////////////////////
//
// The following code is taken from the Tibco GI 3.5.1:
//    http://gi.tibco.com/svnweb/gi/view/trunk/gi/JSX/js/jsx3/util/package.js?rev=552
// and has been modified slightly.
// License: BSD License
////////////////////////////////////////////////////////////////////////////////


/*
-------------------------------------------------------------------------------
TIBCO General Interface(TM) - Professional Edition - BSD License

Copyright (c) 2001-2007, TIBCO Software Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
--Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
--Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
--The name of TIBCO Software Inc. may not be used to endorse or
  promote products derived from this software without specific prior
  written permission of TIBCO Software Inc.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
*/

if (!this.Base64) {

    Base64 = function () {
      /** @private @jsxobf-clobber */
      var _BASE64S = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

      return {    
        /**
         * Returns the result of encoding <code>s</code> to its base-64 equivalent.
         * @param s {String}
         * @return {String}
         * @since 3.2
         */
        strEncode: function(s) {
          var base64s = _BASE64S;
          var buffer = new Array(Math.ceil(s.length * 4 / 3));
      
          var i = 0, c = 0, length = s.length;
          for (; i <= length - 3; i += 3) {
            var bits = (s.charCodeAt(i)   & 0xff) << 16 |
                       (s.charCodeAt(i+1) & 0xff) << 8  |
                       (s.charCodeAt(i+2) & 0xff);
      
            buffer[c++] = base64s.charAt((bits & 0xfc0000) >> 18);
            buffer[c++] = base64s.charAt((bits & 0x03f000) >> 12);
            buffer[c++] = base64s.charAt((bits & 0x000fc0) >> 6);
            buffer[c++] = base64s.charAt((bits & 0x00003f));
          }
      
          if (i < length) {
            var dual = i < length - 1;
      
            var bits = (s.charCodeAt(i) & 0xff) << 16;
            if (dual)
              bits |= (s.charCodeAt(i+1) & 0xff) << 8;
      
            buffer[c++] =   base64s.charAt((bits & 0xfc0000) >> 18);
            buffer[c++] =   base64s.charAt((bits & 0x03f000) >> 12);
            if (dual)
              buffer[c++] = base64s.charAt((bits & 0x000fc0) >> 6);
            else
              buffer[c++] = "=";
            buffer[c++] = "=";
          }
      
          return buffer.join("");
        },
      
        /**
         * Returns the result of decoding <code>s</code> from its base-64 equivalent.
         * @param s {String}
         * @return {String}
         * @since 3.2
         */
        strDecode: function(s) {
          var base64s = _BASE64S;
          var buffer = new Array(Math.ceil(s.length / 4));
      
          //declare variables
          var i = 0, c = 0, length = s.length;
          for (; i < length; i += 4) {
            var bits = (base64s.indexOf(s.charAt(i))   & 0xff) << 18 |
                       (base64s.indexOf(s.charAt(i+1)) & 0xff) << 12 |
                       (base64s.indexOf(s.charAt(i+2)) & 0xff) <<  6 |
                       (base64s.indexOf(s.charAt(i+3)) & 0xff);
      
            buffer[c++] = String.fromCharCode((bits & 0xff0000) >> 16, (bits & 0xff00) >> 8, bits & 0xff);
          }
      
          if (s.charCodeAt(i-2) == 61)
            buffer[c-1] = buffer[c-1].substring(0,1);
          else if (s.charCodeAt(i-1) == 61)
            buffer[c-1] = buffer[c-1].substring(0,2);
      
          return buffer.join("");
        }
      };
    }();
};
